
package com.ys.chatserver;

import com.alibaba.fastjson.JSON;
import com.eva.epc.common.util.RestHashMap;
import com.eva.framework.HttpController;
import com.eva.framework.Processor;
import com.eva.framework.Processor.User;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dto.DataFromClient;
import com.eva.framework.dto.DataFromServer;
import com.eva.framework.dto.LoginInfo2;
import com.eva.framework.dto.LogoutInfo;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.google.gson.Gson;
import com.ys.chatserver.cache.UsersInfoCacheProvider;
import com.ys.chatserver.common.dto.AutoUpdateInfoFromServer;
import com.ys.chatserver.common.dto.cnst.EncodeConf;
import com.ys.chatserver.common.dto.cnst.MyProcessorConst;
import com.ys.chatserver.common.dto.cnst.OperateLog;
import com.ys.chatserver.common.dto.cnst.RetResult;
import com.ys.chatserver.http.logic.*;
import com.ys.chatserver.http.logic.dto.RosterElementEntity;
import com.ys.chatserver.http.logic.filter.JwtHelper;
import com.ys.chatserver.http.logic.filter.JwtParam;
import com.ys.chatserver.im.util.ChatServerUtils;
import com.ys.chatserver.im.util.MessageHelper;
import com.ys.chatserver.tool.EmptyUtils;
import com.ys.chatserver.tool.IpAddressUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.Vector;

/**
 * RainbowChat的服务端HTTP REST接口服务的MVC核心控制器实现类（用于APP等通过HTTP POST方式）.
 * <p>
 * <b>说明：</b>本子类只需实现用户级的业务逻辑.
 * <p>
 * <b>调用：</b>HTTP的POST方式。<br>
 * <b>用途：</b>本类主要用于实现APP端的http rest接口调用，服务端返回格式为JSON。
 *
 * @since 3.0
 */
public class MyControllerJSON extends HttpController {
    private static final long serialVersionUID = 1L;

    /**
     * 用于执行客户端登陆请求时的客户端程序版本检查的辅助对象
     */
    private ClientAPKVersionChecker clientAPKVersionChecker = null;
    private final LogicProcessor2 logicProcessor = new LogicProcessor2();
    private final IMAdminProcessor adminProcessor = new IMAdminProcessor();
    private final FileInfoProcessor fileInfoProcessor = new FileInfoProcessor();
    private final GroupChatProcessor groupChatProcessor = new GroupChatProcessor();
    private final GpsProcessor gpsProcessor = new GpsProcessor();
    private final UserInfoProccessor userInfo = new UserInfoProccessor();
    private final StructureInfoProccessor structureInfoProcessor = new StructureInfoProccessor();
    private final StructureUserProccessor structureUserProccessor = new StructureUserProccessor();

    private final DevInfoProccessor devInfoProccessor = new DevInfoProccessor();

    private final CaseInfoProccessor caseInfoProccessor = new CaseInfoProccessor();

    private final CaseDevProccessor caseDevProccessor = new CaseDevProccessor();
    private final HumanFaceProcessor humanFaceProcessor = new HumanFaceProcessor();

    /**
     * <p>
     * MVC框架核心调度处理方法.
     * <p>
     * RainbowChat的所有http rest数据接口请求，将由本方法捕获，并进行业务调度和处理。
     *
     * @param dfc 封装了客户端发过来的相关数据（本方法中是必须的参数）
     * @param req 客户端请求实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @param res 客户端响应实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @return 处理完返回的对象（要给客户端的）
     * @throws Exception 本方法调用中出现任何异常时都会抛出，方法将中断执行
     */
    @Override
    protected Object dispatch(DataFromClient dfc, HttpServletRequest req,
                              HttpServletResponse res) throws Exception {
        Object retObj = null;
        int processor_id = dfc.getProcessorId();

        LoggerFactory.getLog().debug("〖 [HTTP][客户端类型:" + printOS(dfc.getDevice()) + "] 捕获REST【接口"
                + printRestNum(processor_id, dfc.getJobDispatchId(), dfc.getActionId())
                + "】的请求，安全token=" + dfc.getToken() + " 〗");
        // Rest框架需要首先对客户端发送过来的数据进行类型检查，防止Android、标准Java这种java系
        // 的平台发过来的是除String之外的平台发过来的是除String之外的Java序列化对象而非JSON文本
        if ((dfc.getNewData() != null && !(dfc.getNewData() instanceof String))
                || (dfc.getOldData() != null && !(dfc.getOldData() instanceof String))) {
            String ds = "[HTTP]Rest框架要求客户端发过来的数据必须是JSON文本！而现在拿到的是：" +
                    "newData=" + (dfc.getNewData() != null ? dfc.getNewData().getClass().getName() : "")
                    + ", oldData=" + (dfc.getOldData() != null ? dfc.getOldData().getClass().getName() : "");
            LoggerFactory.getLog().error(ds);
            throw new RuntimeException(ds);
        }

        Processor processor = null;
        // 【接口1009】登陆认证和版本检查综合接口（Android专用）
        if (processor_id == MyProcessorConst.PROCESSOR_LOGIN$CHECKUPDATE_4A) {
            LoginInfo2 ai = JSON.parseObject((String) dfc.getNewData(), LoginInfo2.class);
            String uidOrmail = ai.getLoginName();
            String clientCurrentVersion = ai.getClientVersion();
            String oprContent = ai.getDeviceInfo();
            LoggerFactory.getLog().debug("[HTTP]<版本检查START> ..................用户：" + uidOrmail);
            //** 开始进行版本检查
            AutoUpdateInfoFromServer aui = getClientAPKVersionChecker().checkClientAPKVersion(clientCurrentVersion);
            //客户端需要更新
            if (aui.isNeedUpdate() && !("-1".equals(ai.getClientVersion()))) {
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_NEED_UPDATE, uidOrmail, oprContent);
                addOperateLog(oprLog);
                LoggerFactory.getLog().debug("[HTTP]<版本检查END> " + uidOrmail + "需首先更新程序版本("
                        + "手机当前版本：" + clientCurrentVersion + "，需要更新到版本：" + aui.getLatestClientAPKVercionCode()
                        + ")，更新信息将立即返回给客户端，RETURN.");
//				return aui;
            }
            //密码错误3次 账号锁定验证
            Integer tmp = MyProcessorConst.timerMap.get(uidOrmail);
            RestHashMap retMap = RestHashMap.n();
//            if (!Objects.isNull(tmp) && tmp == 3) {
//                Long time = MyProcessorConst.timerMap.checkKeyExpireTime(uidOrmail);
//                retMap.p("fail-there", time);
//                return JSON.toJSONString(retMap);
//            }
            retMap.p("update_info", JSON.toJSONString(aui));

            //** 登陆验证
            LoggerFactory.getLog().debug("[HTTP]<登陆时：登陆验证START> " + uidOrmail + "，接下来将进入真正的登陆验证流程.");
            // 登陆认证：返回null则表示认证实现，否则返回的是登陆用户的个人信息
            RosterElementEntity rosterElementEntity = queryLoginUser(ai);
            if (rosterElementEntity != null && StringUtils.isNotEmpty(rosterElementEntity.getLatest_login_time())) {
                return JSON.toJSONString(RetResult.failResult("登录失败,该账号已经有人在登录!", 2000));
            }
            RosterElementEntity authedUser = authrizationLogin(ai, getIpAddr(req), ai.isAndroid(), rosterElementEntity, req);
            if (authedUser == null) {
                LoggerFactory.getLog().debug("[HTTP]<登陆时：登陆验证END> " + uidOrmail + "的账号无效或登陆密码不正确(在其它手机修改过？).");
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_FALIURE, uidOrmail, oprContent);
                addOperateLog(oprLog);
                //记录失败次数
//                Integer num = MyProcessorConst.timerMap.get(uidOrmail);
//                if (Objects.isNull(num)) {
//                    MyProcessorConst.timerMap.put(uidOrmail, 1);
//                } else {
//                    ++num;
//                    if (num == 3) {
//                        MyProcessorConst.timerMap.put(uidOrmail, 3, 300L);
//                        retMap.p("fail-there", 300);
//                        return JSON.toJSONString(retMap);
//                    } else {
//                        MyProcessorConst.timerMap.put(uidOrmail, num);
//                    }
//                }
            } else {
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date expireTime = format.parse(authedUser.getExpireTime());
                Date nowTime = new Date();
                if (expireTime.before(nowTime)) { //过期时间小于当前时间  账号过期
                    return JSON.toJSONString(retMap);
                }
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_SUCCESS, uidOrmail, oprContent);
                addOperateLog(oprLog);
            }

            //** 返回值
            if (authedUser != null) {
                retMap.p("authed_info", JSON.toJSONString(authedUser));
                retMap.p("tcp_port", BaseConf.getInstance().getNGINX_IM_SERVER_WEBSOCKET_LISTENNING_PORT());
                retMap.p("jk", false);
                if (authedUser.getUserType() == 1) {
                    String userUid = authedUser.getUser_uid();
                    Vector<Vector<?>> vres = LogicProcessor2.db.queryData("select c.base_id,c.node_pid,c.node_id from chat_group_node c left join g_users g on  g.user_uid = c.user_uid" +
                            " where c.node_type = 'USER' and c.g_status = 1 and g.del_flag = 0 and g.user_type = 1 and c.user_uid = '" + userUid + "'");
                    retMap.put("groupId", vres.get(0).get(0).toString());
                    retMap.put("channelId", vres.get(0).get(1).toString());
                    retMap.put("nodeId", vres.get(0).get(2).toString());
                }
                //组织推送上线信息
                sendOnLineMsg(authedUser.getUser_uid());
            }

            return JSON.toJSONString(retMap);
        }
        // 【接口1010】客户端版本更新检查接口（Android专用）
        else if (processor_id == MyProcessorConst.PROCESSOR_CHECK_UPDATE_4A) {
            AutoUpdateInfoFromServer aui = new AutoUpdateInfoFromServer();
            if (dfc.getNewData() != null) {
                String clientCurrentVersion = (String) dfc.getNewData();
                //开始进行版本检查
                aui = getClientAPKVersionChecker().checkClientAPKVersion(clientCurrentVersion);
            }

            return JSON.toJSONString(aui);
        }
        // 【接口1013】客户端登陆接口（Android\ios均可使用）
        else if (processor_id == MyProcessorConst.PROCESSOR_LOGIN_4ALL) {

            LoginInfo2 ai = JSON.parseObject((String) dfc.getNewData(), LoginInfo2.class);

            String uidOrmail = ai.getLoginName();
            String oprContent = ai.getDeviceInfo();
//            Integer tmp = MyProcessorConst.timerMap.get(uidOrmail);
//            if (!Objects.isNull(tmp) && tmp == 3) {
//                Long time = MyProcessorConst.timerMap.checkKeyExpireTime(uidOrmail);
//                return JSON.toJSONString(RetResult.failResult("登录失败!账号被锁定剩余时间" + time + "s"));
//            }

            //** 登陆验证
            //
            LoggerFactory.getLog().debug("[HTTP]<登陆时：通用登陆验证START> " + uidOrmail + "，接下来将进入真正的登陆验证流程.");
            // 登陆认证：返回null则表示认证实现，否则返回的是登陆用户的个人信息
            RosterElementEntity rosterElementEntity = queryLoginUser(ai);
            if (rosterElementEntity != null && StringUtils.isNotEmpty(rosterElementEntity.getLatest_login_time())) {
                return JSON.toJSONString(RetResult.failResult("登录失败,该账号已经有人在登录!", 2000));
            }
            RosterElementEntity authedUser = authrizationLogin(ai, getIpAddr(req), ai.isAndroid(), rosterElementEntity, req);
            if (authedUser == null) {
                LoggerFactory.getLog().debug("[HTTP]<登陆时：通用登陆验证END> " + uidOrmail + "的账号无效或登陆密码不正确(在其它手机修改过？).");
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_FALIURE, uidOrmail, oprContent);
                addOperateLog(oprLog);
                //记录失败次数
                return JSON.toJSONString(RetResult.failResult("登录失败,密码错误!"));
//                Integer num = MyProcessorConst.timerMap.get(uidOrmail);
//                if (Objects.isNull(num)) {
//                    MyProcessorConst.timerMap.put(uidOrmail, 1);
//                    return JSON.toJSONString(RetResult.failResult("登录失败,密码错误!"));
//                } else {
//                    ++num;
//                    if (num == 3) {
//                        MyProcessorConst.timerMap.put(uidOrmail, 3, 300L);
//                        return JSON.toJSONString(RetResult.failResult("登录失败!账号被锁定!剩余时间300s"));
//                    } else {
//                        MyProcessorConst.timerMap.put(uidOrmail, num);
//                        return JSON.toJSONString(RetResult.failResult("登录失败,密码错误!"));
//                    }
//                }
            } else {

                // 记录操作日志
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_SUCCESS, uidOrmail, oprContent);
                addOperateLog(oprLog);
                //组织推送上线信息
                sendOnLineMsg(authedUser.getUser_uid());
            }

            //** 返回值
            return JSON.toJSONString(RetResult.successResult("登录成功!", authedUser));
        }
        // 【接口-2】客户端注销登陆接口（Android\ios\web均可使用）
        else if (processor_id == MyProcessorConst.PROCESSSOR_LOGOUT) {
            LogoutInfo ao = JSON.parseObject((String) dfc.getNewData(), LogoutInfo.class);
            authrizationLogout(ao);
            return null;
        } else {
            switch (processor_id) {
                // IM的基本业务逻辑接口处理器
                case MyProcessorConst.PROCESSOR_LOGIC:
                    processor = logicProcessor;
                    break;
                // 专用于后台管理端的一些特权接口，以后代码可考虑独立出去，保证安全性
                case MyProcessorConst.PROCESSOR_ADMIN:
                    processor = adminProcessor;
                    break;
                // 大文件信息查询接口的处理器
                case MyProcessorConst.PROCESSOR_FILE:
                    processor = fileInfoProcessor;
                    break;
                // 群组聊天相关管理接口的处理器
                case MyProcessorConst.PROCESSOR_GROUP_CHAT:
                    processor = groupChatProcessor;
                    break;
                case MyProcessorConst.PROCESSOR_GPS:
                    processor = gpsProcessor;
                    break;
                case MyProcessorConst.PROCESSOR_USER:
                    processor = userInfo;
                    break;
                case MyProcessorConst.PROCESSOR_STRUCTURE:
                    processor = structureInfoProcessor;
                    break;
                case MyProcessorConst.PROCESSOR_STRUCTURE_USER:
                    processor = structureUserProccessor;
                    break;
                case MyProcessorConst.PROCESSOR_DEV:
                    processor = devInfoProccessor;
                    break;
                case MyProcessorConst.PROCESSOR_CASE:
                    processor = caseInfoProccessor;
                    break;
                case MyProcessorConst.PROCESSOR_CASE_DEV:
                    processor = caseDevProccessor;
                    break;
                case MyProcessorConst.PROCESSOR_HUMAN_FACE:
                    processor = humanFaceProcessor;
                    break;
                //找不到处理器
                default:
                    throw EndsExceptionFactory.INVALID_PROCESSOR_ID(processor_id);
            }

            String basePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath() + "/";
//			System.out.println("[HTTP]basePath = " +  basePath);
            // 借用一个User对象，仅仅是用来存放Ip地址而已
            User u = new User();
            String token = req.getHeader("token");
            if (EmptyUtils.isNotEmpty(token)) {
                JwtParam jwtParam = JwtHelper.parseJwt(token);
                u.setUserId(jwtParam.getUserId());
                u.setUserMail(jwtParam.getUserMail());
                u.setNickname(jwtParam.getNickname());
                u.setAndroid(jwtParam.getAndrioid());
            } else {
                //通过token获取用户Id
                if (EmptyUtils.isNotEmpty(dfc.getToken())) {
                    JwtParam jwtParam = JwtHelper.parseJwt(dfc.getToken());
                    u.setUserId(jwtParam.getUserId());
                    u.setUserMail(jwtParam.getUserMail());
                    u.setNickname(jwtParam.getNickname());
                    u.setAndroid(jwtParam.getAndrioid());
                }
            }

            u.setIp(getIpAddr(req));
            if (processor != null) {
                if (processor instanceof LogicProcessor2) {
                    retObj = processor.process(dfc.getJobDispatchId()
                            , dfc.getActionId()
                            , dfc.getNewData()
                            , new Object[]{basePath, dfc.getOldData()}
                            , u);
                } else
                    retObj = processor.process(dfc.getJobDispatchId()
                            , dfc.getActionId(), dfc.getNewData()
                            , dfc.getOldData(), u);

                // 返回值类型检查
                if (retObj != null && !(retObj instanceof String)) {
                    String ds = "[HTTP]Rest框架中processor处理完成" +
                            "的返回值必须是JSON文本哦！(retObj=" + retObj.getClass().getName() + ")";
                    LoggerFactory.getLog().error(ds);
                    throw new RuntimeException(ds);
                }
            }

            return retObj;
        }
    }

    private void sendOnLineMsg(String userUid) throws Exception {
        Vector<Vector<?>> vectors = ChatServerUtils.getNotifyUserByUid(userUid);
        if (!vectors.isEmpty()) {
            int userType = ChatServerUtils.getUserType(userUid);
            for (Vector<?> userId : vectors) {
                if (userType == 1) {//设备
                    MessageHelper.sendDevOnlineMessage(userId.get(0).toString(), JSON.toJSONString(userUid), null);
                } else {
                    MessageHelper.sendOnlineMessage(userId.get(0).toString(), JSON.toJSONString(userUid), null);
                }
            }
        }
    }


    /**
     * 获取Android的版本检查对象。
     *
     */
    private ClientAPKVersionChecker getClientAPKVersionChecker() {
        if (clientAPKVersionChecker == null)
            clientAPKVersionChecker = new ClientAPKVersionChecker(this.getServletContext().getRealPath("/"));//项目的发布磁盘目录就是apk文件放置的目录（已含末尾“/“） //ClientAPKVersionChecker.getAPKPath());
        return clientAPKVersionChecker;
    }

    /**
     * 记录用户操作日志。
     */
    public static void addOperateLog(OperateLog log) throws Exception {
        Vector<Object> sqls = new Vector<>();
        Vector<Object> values = new Vector<>();
        // 先把好友从我的好友列表中删除
        sqls.add("INSERT INTO missu_oprlog(user_uid,opr_type,opr_desc,opr_content,opr_bz,opr_time) " +
                "VALUES (?,?,?,?,?," + DBDepend.getDefaultDatetimeFunc() + ")");
        values.add(new Object[]{log.getUser_uid(), log.getOpr_type(), log.getOpr_desc(), log.getOpr_content(), log.getOpr_bz()});
        LogicProcessor2.db.update(sqls, values, true);
    }

    public static RosterElementEntity queryLoginUser(LoginInfo2 ai) throws Exception {
        // 登陆名可以是多种形式
        String uidOrMail = ai.getLoginName();
        // 登陆密码
        String psw = ai.getLoginPsw();

        String baseSql = LogicUtils.getUSER_INFI_BASE_SQL(null) + " where del_flag = 0 and user_status = 1" +
//				" 	and (ltrim(str(user_uid))='"+uidOrMail+"' or user_mail='"+uidOrMail+"')" +
                "	and " + "user_mail='" + uidSQLSave(uidOrMail) + "'" +
                "	and user_psw='" + psw + "'";

        return LogicUtils.constructUserInfo(LogicProcessor2.db.querySingleRowItems(baseSql));
    }

    /**
     * 验证登陆用户的人信息.
     *
     * @param ai 登陆信息封装对象
     * @return 登陆成功则返回此用户的完整个人信息，登陆不成功则返回null
     */
    public static RosterElementEntity authrizationLogin(LoginInfo2 ai, String ip, boolean isAndroid, RosterElementEntity userAuthedInfo, HttpServletRequest request) throws Exception {
        if (userAuthedInfo != null) {
            Date loginTime = new Date();
            LogicProcessor2.db.update("update g_users set latest_login_time=" + DBDepend.getDefaultDatetimeFunc() +
                            ", latest_login_ip=? where del_flag = 0 and user_uid=?"
                    , new Object[]{ip, userAuthedInfo.getUser_uid()}, false);

            // 目前android登陆时才需要进行手机端版本更新检查（ios的app store自动完成、web里不需要版本更新）
            if (isAndroid) {
                //************************************** 【3】保留字段的使用 **********************************************
                // ## 【开启即生效】字段_ex3用于开启客户端的强制更新功能，开启的逻辑是当客户端的当前版本未更新到此版本时，无条件阻止客户登陆（客户端自版本42起已启用））
                userAuthedInfo.setMustVersion4A("410");// 表示要强制更新的版本号(小于此版本号的都需要强制更新)
//				userAuthedInfo.setMustVersionDesc4A("This version includes important updates, you must complete update before login!");// 将用于客户端在ui上的提示信息
                userAuthedInfo.setMustVersionDesc4A("您的版本已过时，请升级客户端后再试，感谢理解！");// 将用于客户端在ui上的提示信息
            }
            // $$ 【自20170216 RainbowChat4.0生效】目前本字段为保留字段，建议2次开发时，由开发者自行决定
            //      token的生成规则和使用逻辑。此token将用于返回客户端后连接到IM服务器时使用，此token将决
            //      定IM即时通讯通道的安全性。
            JwtParam jwtParam = new JwtParam();
            jwtParam.setUserId(userAuthedInfo.getUser_uid());
            jwtParam.setLoginTime(loginTime);
            jwtParam.setUserMail(userAuthedInfo.getUser_mail());
            jwtParam.setNickname(userAuthedInfo.getNickname());
            jwtParam.setAndrioid(IpAddressUtils.isAndroid(request));
            String subject = JwtHelper.generalSubject(jwtParam);
            String jwt = JwtHelper.createJWT(JwtHelper.JWT_ID, subject, ai.isDevice());
            userAuthedInfo.setToken(jwt);

            //************************************** 【4】更新当前登陆用户缓存 **********************************************
            //** 【高速缓存】即时更新用户缓存数据（因为用户信息中的ios设备的消息推送token字段可能已经更新了）
            UsersInfoCacheProvider.getInstance().reloadOneFromDB(userAuthedInfo.getUser_uid());
        }
        return userAuthedInfo;
    }

    /**
     * 注销登陆.
     * <p>
     * 注销登陆可在客户端：退出APP、切换APP时调用。除了iOS设备外，暂时其它客户端不强制非得进行注销登陆。
     *
     * @param ao 注销登陆信息封装对象
     * @since 4.3
     */
    public static void authrizationLogout(LogoutInfo ao) throws Exception {
        LoggerFactory.getLog().debug("[HTTP]<注销登陆> 由uid=" + ao.getUid() + "[客户端类型：" + ao.getOsType() + "] 的账号发出的注销登陆请求 ...");

        // 如果是ios设备的注销登陆：则设置它的设备token为空（这样就收不到ios的离线消息推送了）
        if (ao.isIOS()) {
            if (!ao.isDontClearDeviceToken()) {
                LogicProcessor2.db.update("update g_users set ios_device_token=null where del_flag = 0 and user_uid=?"
                        , new Object[]{ao.getUid()}, false);

                //** 【高速缓存】即时更新用户缓存数据（注销的时候，该用户在db中的ios device token字段已被置空了）
                UsersInfoCacheProvider.getInstance().reloadOneFromDB(ao.getUid());
            }
        }
        // 其它客户端类型在注销登陆时需要做的事情
        else {
            if (!ao.isDontClearDeviceToken()) {
                //设置登录时间为空，在通过token校验时，token校验将不会通过，相当于置空了token
                LogicProcessor2.db.update("update g_users set latest_login_time2=null where del_flag = 0 and user_uid=?"
                        , new Object[]{ao.getUid()}, false);

                //** 【高速缓存】即时更新用户缓存数据（注销的时候，该用户在db中的ios device token字段已被置空了）
                UsersInfoCacheProvider.getInstance().reloadOneFromDB(ao.getUid());
            }
        }

        // 加入用户操作日志
        OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGOUT_HTTP_LOGIN, ao.getUid(), ao.getDeviceInfo());
        addOperateLog(oprLog);
    }


    /**
     * 将数据对象发回给PC客户端.
     *
     * @param res           客户端响应HttpServletResponse对象
     * @param objFromServer 要发送的可系列化的数据对象
     */
    // 重写父类方法的目的是将父类中发出的是java序列化对象的方式改为JSON文本方式（以便跨平台（非java平台））
    @Override
    protected void sendToClient(HttpServletResponse res, DataFromServer objFromServer) throws IOException {
        String toCient = new Gson().toJson(objFromServer);
        // 发出的是JSON文本描述的DataFromClient对象
        byte[] bs = toCient.getBytes(EncodeConf.ENCODE_TO_CLIENT);//
        OutputStream out = res.getOutputStream();
        out.write(bs);
        out.flush();
        out.close();

        LoggerFactory.getLog().info("【<== 处理完成-返回JSON结果】:" + toCient);
    }

    /**
     * 读取PC客户端传过来的数据对象。
     *
     * @param req 客户端请求HttpServletRequest对象
     * @return 返回读取出来的客户端数据对象
     * @throws IOException            如果req.getInputStream()出现异常时抛出
     * @throws ClassNotFoundException 如果is.readObject()出现异常时抛出
     */
    // 重写父类方法的目的是将父类中接收的是java序列化对象的方式改为JSON文本方式（以便跨平台（非java平台））
    @Override
    protected DataFromClient recieveFromClient(HttpServletRequest req) throws IOException, ClassNotFoundException {
        InputStream is = req.getInputStream();
        int ch;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while ((ch = is.read()) != -1)
            bos.write(ch);
        is.close();
        bos.close();
        // 接收的数据是JSON文本描述的DataFromClient对象
        String res = SQLSave(bos.toString(EncodeConf.DECODE_FROM_CLIENT));
//		System.out.println(res);
        LoggerFactory.getLog().info("【==> 收到接口请求-数据原型】:" + res);
        //		System.out.println("getActionId="+dfc.getActionId()
//				+", getJobDispatchId="+dfc.getJobDispatchId()
//				+", getProcessorId="+dfc.getProcessorId()
//				+", getNewData="+dfc.getNewData()
//				);
        return new Gson().fromJson(res, DataFromClient.class);
    }


    // 打印客户端设备类型
    private static String printOS(int osType) {
        if (0 == osType) {
            return "Android";
        } else if (1 == osType) {
            return "iOS";
        } else if (2 == osType) {
            return "Web";
        } else if (3 == osType) {
            return "Device";
        }
        return "未知";
    }

    // 打印rest接口的编号，方便调试
    public static String printRestNum(int processorId, int jobDispatchId, int actionId) {
        String ret = "";
        if (processorId > 0)
            ret += String.valueOf(processorId);
        if (jobDispatchId > 0)
            ret += "-" + String.valueOf(jobDispatchId);
        if (actionId > 0)
            ret += "-" + String.valueOf(actionId);

        return ret;
    }

    /**
     * 严格检查uid内容的合法性，防止被攻击。
     *
     * @param originalUidStr 用户输入的登陆uid原始字符串
     * @return true表示符合规范，否则不规范
     */
    public static boolean checkUidValid(String originalUidStr) {
        return originalUidStr != null && originalUidStr.length() <= 11 && ChatServerUtils.isInteger(originalUidStr);
    }

    /**
     * 强制对用户登录时输入的uid进行sql注入保护（本方法将在全局SQL防注入策略的基础上进一步防护）。
     *
     * @param originalStr 用户输入的登陆uid原始字符串
     * @return 防sql注入处理后的结果
     */
    public static String uidSQLSave(String originalStr) {
        if (originalStr != null) {
            // 主要防护“10000 or 1=1”这种类型的非法uid
            return originalStr.replaceAll(" ", "");
        }
        return originalStr;
    }

    /**
     * 强制保护因程序员的后台SQL语句未使用预编译语句而发生SQL注入攻击的风险。
     *
     */
    public static String SQLSave(String originalStr) {
        if (originalStr != null) {
            // 此替换逻辑来自网上，实测会将整条SQL全部转空，不是最佳逻辑，20190324日已弃用。
            // 比如：此逻辑会导致web端传过来的html转义字符（如“&gt;”）被错误地整块替换掉
            //       newData字段，从而无法成功进行http rest的请求处理！
//			return originalStr.replaceAll(".*([';]+|(--)+).*", " ");

            // 将SQL注入风险关键字符替换成空格，启用于20190324日
            return originalStr.replaceAll("'", " ").replaceAll(";", " ").replaceAll("--", " ");
        }

        return originalStr;
    }

//	public static void main(String[] args)
//	{
//		String s = "select * from users where username='';DROP Database (DB Name) --' and password=''";
//		System.out.println(">>>>>>>>>>>>>>>>>>>"+MyControllerJSON.SQLSave(s));
//	}

    /**
     * ngnix 反向代理获取客户端ip
     *
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}
